from re import search as re_search

from myelin.Ns import MYELIN, FOAF
from myelin.rdf.bindery.Model import Model
from myelin.server.lib.Account import Account, Manager
from myelin.server.core.ft import ModelDriver
from myelin.server.lib.UriScopeResolver import UriScopeResolver
from myelin.lib import Ids

from nodetypes.models.UserAccount import UserAccount
from nodetypes.models.UserPreferences import UserPreferences
from nodetypes.models.UserAgent import UserAgent
from nodetypes.Ns import GRAPHS_NS

from AgentAcls import AgentAcls

from operator import itemgetter

ONTOLOGY = {
    'account-tag' : MYELIN + u'tag',
    'primary-agent': MYELIN + u'primary-agent',
    'password-sha': MYELIN + u'password-sha',
    'disabled': MYELIN + u'disabled',
    'agentIds': MYELIN + u'agentIds',
    'member': FOAF + u'member',
}


class Account(Account):
    
    newAgentAcls = AgentAcls
    newUserAccountDriver = UserAccount.Initialize
    newUserAccount = UserAccount.Retrieve
    
    newAcctsDriver = staticmethod(ModelDriver.FromRepo)
    newAgentsDriver = staticmethod(ModelDriver.FromRepo)
    newAclsDriver = staticmethod(ModelDriver.FromRepo)
    newProfileDriver = staticmethod(ModelDriver.FromRepo)
    newPreferencesDriver = staticmethod(ModelDriver.FromRepo)
    
    newUserAgent = UserAgent.Retrieve
    newUserAgentDriver = UserAgent.Initialize
     
    newAgentId = staticmethod(Ids.NewHash)
    
    domainIdRepl = u"mailto:postmaster@%s"
    accountIdRepl = u"mailto:%s@%s"
    accountUriRepl = u"%s/%s/%s/index#account"
    
    isAdminPatRepl = "(?:\|+|^)%s@%s(?:\|+|$)"
    
    #accountModelName = 'accounts'  # FIXME: add to server config
    itemgetter_0 = itemgetter(0)
    
    def __init__(self, repo, rootUri, domains, graphs):
        assert rootUri[-1] != '/'
        self._repo = repo
        self.rootUri = rootUri
        self.domains = domains
        self._graphs = graphs
        
    def clone(self):
        return self.__class__(
            self._repo,
            self.rootUri,
            self.domains,
            self._graphs
        )
        
    def retrieveAcls(self, nodeId):
        assert hasattr(self, 'ids')
        acls = self.newAgentAcls(
            self.newAclsDriver(
                self._repo,
                self._graphs[GRAPHS_NS + 'app']['acls']
            ),
            self.isAdmin
        )        
        acls.resetBase(nodeId, self.ids)
        return acls
    
    def getAccountFromAgentId(self, agentId):
        # check if current user has rights
        if not self.canRetrieveAccounts():
            return
        
        # get accountId
        driver = self.newAcctsDriver(
            self._repo,
            self._graphs[GRAPHS_NS + 'accounts']['main']
        )
        for accountId in driver.subjectsFromPredAndObjs(
            ONTOLOGY['primary-agent'],            
            (agentId,),
            None):
            
            agentName, domainName = accountId[7:].split('@') # len('mailto:') == 7     
            domain = self.domains.get(domainName)
            if not domain:
                continue
        
            # domain ID
            domainId = self.domainIdRepl % domainName            
            accountUri = self.accountUriRepl % (self.rootUri, domainName, agentName)
            
            # add agent's full name as initial ids
            #ids = [self.GUEST_ACL_ID, agentName + '@' + domainName]
            #rlen = len(self.rootUri) + 1
            #driver = self._agentsDriver.subjectsFromPredAndObjs
            #def _ids(id):
            #    #print 'id: %s' % id
            #    for m in driver(None,None,scope=id + '.memberships'):
            #        # we can optimize
            #        #if __debug__: print "id: %s" % m
            #        name = m[rlen:].split('/')
            #        #print "name: %s" % name
            #        if len(name) == 2:
            #            name = name[0]
            #            if name not in ids:
            #                ids.append(name)
            #            continue
            #        
            #        name = name[-2] + '@' + name[-3]
            #        if name not in ids:
            #            ids.append(name)
            #            _ids(m[:-6]) # remove .agent portion
            #_ids(accountId)
            
            acct = self.clone()
            acct.ids = '|'.join(
                map(
                    self.imtemgetter_0,
                    driver.objectsFromSubAndPred(
                        accountId,
                        ONTOLOGY['agentIds'],
                        accountUri
                    )
                )
            )
            
            acct.domainId = domainId
            acct.accountId = accountId
            #acct.accountUri = accountUri
            acct.agentQName = (unicode(agentName), unicode(domainName))
            acct.agentId = self.newAgentId(accountId) #agentIdRepl % sha_new(accountId).hexdigest()
            
            if re_search(
                self.isAdminPatRepl % (domain[1],domainName),
                self.ids
            ):
                self.isAdmin = True
               
            return acct
    
    def login (self, domainName, agentName, password):        
        domain = self.domains.get(domainName)
        if not domain:
            return False
        
        # domain ID
        domainId = self.domainIdRepl % domainName
        
        # agent's account Id
        accountId = self.accountIdRepl % (agentName, domainName)
        accountUri = self.accountUriRepl % (self.rootUri, domainName, agentName)
        
        # extract for convenience
        driver = self.newAcctsDriver(
            self._repo,
            self._graphs[GRAPHS_NS + 'accounts']['main']
        )
        
        if __debug__:
            print "domainId: %s" % domainId
            print "accountId: %s" % accountId
            print "accountUri: %s" % accountUri
                
        #if driver.contains(
        #    accountId,
        #    ONTOLOGY['disabled'],
        #    None,
        #    None,
        #    accountUri,
        #    DEFAULT_RDFQUERY_FLAGS) or \
        if not driver.contains(
            accountId,
            ONTOLOGY['password-sha'],
            unicode(password),
            None,
            accountUri
            ): 
            if __debug__: print "Invalid password or account disabled"
            return False        
        
        # agent-ids now pre-cached in model
        self.ids = '|'.join(
            map(
                itemgetter(0),
                driver.objectsFromSubAndPred(
                    accountId,
                    ONTOLOGY['agentIds'],
                    accountUri
                )
            )
        )
        
        #ids = set([self.GUEST_ACL_ID, agentName + '@' + domainName])
        #rlen = len(self.rootUri) + 1
        #driver = self._agentsDriver.subjectsFromPredAndObjs
        #def _ids(id):
        #    #print 'id: %s' % id
        #    for m in driver(None,None,scope=id + '.memberships'):
        #        # we can optimize
        #        #if __debug__: print "id: %s" % m
        #        name = m[rlen:].split('/')
        #        #print "name: %s" % name
        #        if len(name) == 2:
        #            name = name[0]
        #            if name not in ids:
        #                ids.append(name)
        #            continue
        #        
        #        name = name[-2] + '@' + name[-3]
        #        if name not in ids:
        #            ids.append(name)
        #            _ids(m[:-6]) # remove .agent portion
        #_ids(accountId)
        
        #self.ids = ids
        self.domainId = domainId
        self.accountId = accountId
        #self.accountUri = accountUri
        self.agentQName = (unicode(agentName), unicode(domainName))
        self.agentId = self.newAgentId(accountId) #agentIdRepl % sha_new(accountId).hexdigest()
        if re_search(
            self.isAdminPatRepl % (domain[1],domainName),
            self.ids
        ):
            self.isAdmin = True
        return True
    
    def checkLogin(self, domainName, agentName, password):
        domain = self.domains.get(domainName)
        if not domain:
            return False
        
        # domain ID
        domainId = self.domainIdRepl % domainName
        
        # agent's account Id
        accountId = self.accountIdRepl % (agentName, domainName)
        accountUri = self.accountUriRepl % (domainName, agentName)
        
        if __debug__:
            print "domainId: %s" % domainId
            print "accountId: %s" % accountId
            print "accountUri: %s" % accountUri
            
        # extract for convenience
        #driver = self._acctsDriver.contains        
        
        # verify password
        #if not driver(
        #    accountId,
        #    ONTOLOGY['disabled'],
        #    None,
        #    None,
        #    accountUri,
        #    DEFAULT_RDFQUERY_FLAGS) and \        
        if self.newAcctsDriver(
            self._repo,
            self._graphs[GRAPHS_NS + 'accounts']['main']
        ).contains(
            accountId,
            ONTOLOGY['password-sha'],
            password,
            None,
            accountUri):
            return True
        
        if __debug__: print "Invalid password or account disabled"
        return False
    
        
    def getPreferences(self, defaults):
        if hasattr(self, '_preferences'):
            return self._preferences
        app_key = defaults.app_key
        
        model = self.newUserAccount(
            self.newUserAccountDriver(
                self.newPreferencesDriver(
                    self._repo,
                    self._graphs[GRAPHS_NS + 'accounts']['main']
                ),
                uriScopeResolver = UriScopeResolver.FromRepo(self._repo)
            ),
            self.accountId
        )
        for p in model.mPreferences_For:
            if app_key in p.mTag:
                self._preferences = p
                return p
                
    def createPreferences(self, defaults):
        prefs = self.getPreferences(defaults)
        if prefs:
            return prefs
        
        ## this is just pseudocode for now ...
        #repo = self._repo
        #prefId = self.accountId + '.preferences/' + app_key
        #
        #repo.createContainer(prefId)
        #repo.createDocument(
        #    prefId + '/index',
        #    self.PREFERENCE_INDEX_DOCUMENT % {'node-id': prefId}
        #)
        #repo.createDocument(
        #    prefId + '/.settings',
        #    self.PREFERENCES_DEFAULT_SETTINGS
        #)
        #
        #p = None
        #for a in self.agentModel.mPrimary_Account:
        #    a.rdf_add('mPreferences', prefId)
        #    p = a.mPreferences
        #if p:    
        #    self._preferences = p
        #return p        
    
        
    def getProfile(self, context, locale):
        defaults = context.defaults
        #root = defaults.document_root
        
        agentModel = context.server.graphs[GRAPHS_NS + 'profiles']['published']
        if agentModel:
            #print "here 1"
            agentModel = self.newUserAgent(
                self.newUserAgentDriver(
                    self.newProfileDriver(
                        context.repo,
                        agentModel
                    )
                ),
                self.agentId
            )
        else:
            #print "here 2"
            agentModel = self.newUserAgent(
                context.dbDriver,
                self.agentId
            )       
        
        for p in agentModel.mProfile:
            #if p.rdf_getDomain().startswith(root):
            #    print "here 3: %s" % p.rdf_value
            if locale in p.dcLanguage:
                return p
            for t in p.mTranslation:
                if locale in t.dcLanguage:
                    return t
            #print "here 4: %s, %s, %s" % (p.rdf_value, p.rdf_domain, root)
    

class Manager(Manager):
    
    newAccount = Account
    
    def __init__(self, rootUri, domains):
        self.rootUri = rootUri
        self.domains = domains
        
    def getAccount(self, repo, server):
        # verify login
        return self.newAccount(
            repo,
            self.rootUri,
            self.domains,
            server.graphs
        )
    
    def isValidDomain(self, domain):
        return domain in self.domains
    
    
# cleanup
del Ids